#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Client.h>
#include <Kharlia/Engine/Engine.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Engine/Drawable.h>
#include <Kharlia/Engine/RenderView.h>
#include <Kharlia/Engine/TileData.h>
#include <Kharlia/Engine/Level.h>
#include <Kharlia/Engine/Actor.h>

#include <RendererModules/OpenGL/CEGUIOpenGLTexture.h>

#include <cmath>

#define TILE_W 16
#define TILE_H 16
#define TILE_EX 2

#define TILE_W_F 16.0f
#define TILE_H_F 16.0f
#define TILE_EX_F 2.0f

#define TILE_VERTEX_COUNT 4
#define TILE_VERTEX_SIZE 2
#define TILE_TEX_COORD_COUNT 4
#define TILE_TEX_COORD_SIZE 2

#define CLAMP_RENDER_POSITION 0

namespace kharlia { namespace engine {

static const bool DEBUG_GROUP_COLORING = false;

struct FVertex {
    float X, Y, Z;
    uint32 Color;
    float S, T;
    uint8 Padding[8];
};

C_ASSERT(sizeof(FVertex) == 32);

/// Contains cached rendering information for a tile layer.
/// This is attached to the tile layer for future use.
///
/// Since tile layers are a constant size, a default vertex buffer is
/// generated for use by all tile layers, with only the texture coordinates
/// being unique to instances.
struct FTileLayerRenderInfo: public FRenderInfo {
    weak_ptr<CClient> Client;
    GLfloat Z;
    GLenum RenderMode;

    GLuint VertexBuffer,
           TexCoordBuffer;

    GLint  VertexSize,
           VertexCount,
           VertexOffset,
           TexCoordSize,
           TexCoordCount,
           TexCoordOffset;

    bool   VertexBufferOwned,
           TexCoordBufferOwned;

    // TODO: Have this store a weak reference to the client
    virtual ~FTileLayerRenderInfo() {
        // The client would have deleted the buffers on its own if it no
        // longer exists
        shared_ptr<CClient> cl = Client.lock();
        if (cl && cl->GetInited()) {
            if (VertexBufferOwned) {
                cl->RnUnregisterVertexBuffers(1, &VertexBuffer);
                glDeleteBuffersARB(1, &VertexBuffer);
            }
            if (TexCoordBufferOwned) {
                cl->RnUnregisterVertexBuffers(1, &TexCoordBuffer);
                glDeleteBuffersARB(1, &TexCoordBuffer);
            }
        }
    }
};

struct FTilesetRenderInfo: public FRenderInfo {
    ui::Texture* Texture;

    FTilesetRenderInfo(): Texture(NULL) {}
};

struct FEditorRenderInfo: public FRenderInfo { 
    GLfloat* GridVerticies;
    GLfloat SelectionVerticies[8];

    FEditorRenderInfo(): GridVerticies(NULL) {}
};

//bool BlendCompare(const Blended& lhs, const Blended& rhs) {
//    return lhs.Target->Compare(rhs.Target);
//}

//void Engine::FreeRenderInfo(FTileLayer* t) {
//    FTileLayerRenderInfo* ri = reinterpret_cast<FTileLayerRenderInfo*>(t->mRenderInfo);
//    if (t != NULL) {
//        delete ri;
//        t->mRenderInfo = NULL;
//    }
//}
//
//void Engine::FreeRenderInfo(AActor* a) {
//}

void ClampRenderPosition(FVector3F& pos) {
#if CLAMP_RENDER_POSITION
    static const float pxu = 1.0f / 16.0f;
    float fx = floorf(pos.X);
    float fy = floorf(pos.Y);
    pos.X = fx + floorf((pos.X - fx + (pxu / 2.0f)) * 16.0f) * pxu;
    pos.Y = fy + floorf((pos.Y - fy + (pxu / 2.0f)) * 16.0f) * pxu;
#endif
}

void ClampRenderPosition(FVector2F& pos) {
#if CLAMP_RENDER_POSITION
    static const float pxu = 1.0f / 16.0f;
    float fx = floorf(pos.X);
    float fy = floorf(pos.Y);
    pos.X = fx + floorf((pos.X - fx + (pxu / 2.0f)) * 16.0f) * pxu;
    pos.Y = fy + floorf((pos.Y - fy + (pxu / 2.0f)) * 16.0f) * pxu;
#endif
}

void CClient::RnDrawScene(shared_ptr<ACamera> camera, const FRectF& vp) {
    assert(mRnRendering);
    if (!mRnTilesetTexture)
        return;

    const shared_ptr<CLevel>& level = camera->GetOwner()->GetLevel();
    if (!level)
        throw RuntimeError("camera is not in a level");

    FVector3F cameraPos;
    if (camera->GetTarget()) {
        if (camera->GetTarget()->GetOwner()->GetLevel() != level)
            throw RuntimeError("A camera must be in the same level as its target");
        cameraPos = camera->GetTarget()->GetPosition();
    }
    else {
        shared_ptr<ATransform> pt = camera->GetOwner()->GetTransform();
        if (pt)
            cameraPos = pt->GetPosition();
        else
            return;
    }
    // HACK: Clamping to the nearest pixel avoids a strange graphical error
    //       with tile texture coordinates.
    ClampRenderPosition(cameraPos);

    FSizeF levelSize = level->GetSize();
    FSizeF groupSize = level->GetTileGroupSize();

    // left, top, right, and bottom visible level coordinates (in tiles)
    float vcl = cameraPos.X - ((vp.Size.Width / 2.0f) / TILE_W_F);
    float vct = cameraPos.Y - ((vp.Size.Height / 2.0f) / TILE_H_F);

    float vcr = vcl + (vp.Size.Width / TILE_W_F);
    float vcb = vct + (vp.Size.Height / TILE_H_F);
        
    // top left and bottom right tilegroup positions on the level
    float tlpx = floorf(vcl / groupSize.Width);
    float tlpy = floorf(vct / groupSize.Height);
    float brpx = floorf(vcr / groupSize.Width);
    float brpy = floorf(vcb / groupSize.Height);

    // number of visible tilegroups
    int vsw = int(brpx - tlpx) + 1;
    int vsh = int(brpy - tlpy) + 1;

    // Configure OpenGL
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glViewport(
        GLint(vp.GetTop()),
        GLint(mViewportSize.Height - vp.GetBottom()),
        GLsizei(vp.GetWidth()),
        GLsizei(vp.GetHeight())
    );
    //glEnable(GL_TEXTURE_2D);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    //glEnable(GL_DEPTH_TEST);
    //glDepthFunc(GL_LESS);
    //glDepthMask(GL_TRUE);
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_GREATER, 0.0f);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Set up an orthographic projection to match tile coordinates on the level
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(vcl, vcr, vcb, vct, 1.0, 0.0);

    try {
        bool col = false;
        FVector2I tgp;

        // Draw the tile layers for layer 0
        CLevel::LayerInfoConstIter it;
        for (it = level->GetLayerInfos().begin(); it != level->GetLayerInfos().end(); it++) {
            assert(it->first == it->second->Index);
            assert(it->first < 16);

            FLayerInfo* layerInfo = it->second.get();
            if (!layerInfo->Visible)
                continue;
            
            glColor4ub(layerInfo->Red, layerInfo->Green, layerInfo->Blue, layerInfo->Alpha);
            
            for (int y = 0; y < vsh; y++) {
                for (int x = 0; x < vsw; x++) {
                    // position of tilegroup on the level in tilegroups
                    //PointI spos(tlp + PointI(x, y));
                    float spx = tlpx + float(x);
                    float spy = tlpy + float(y);
                    if (spx < 0.0f || spx >= levelSize.Width ||
                        spy < 0.0f || spy >= levelSize.Height)
                    {
                        continue;
                    }
                    tgp.X = int(spx);
                    tgp.Y = int(spy);
                    const shared_ptr<FTileGroup>& group = camera->GetOwner()->GetLevel()->GetTileGroup(tgp);
                    const shared_ptr<FTileLayer>& layer = group->GetLayer(it->first);
                    // position of the tilegroup on the level in tiles (real position)
                    float srx = spx * groupSize.Width;
                    float sry = spy * groupSize.Height;

                    glPushMatrix();
                    glTranslatef(srx, sry, 0.0f);

                    if (DEBUG_GROUP_COLORING) {
                        // level color highlighting for debugging
                        if (col)
                            glColor4f(0.75f, 0.0f, 0.0f, 1.0f);
                        else
                            glColor4f(0.0f, 0.0f, 0.75f, 1.0f);
                        col = !col;
                    }

                    RnDrawTileLayer(*layer.get());

                    glPopMatrix();
                }
                if (DEBUG_GROUP_COLORING)
                    col = !col;
            }
            // Draw the actors for layer 0
            RnDrawLevelActors(*level.get(), 0, vcl, vct, vcr, vcb);
        }
        // Draw the editor brush
        if (mEngine->GetEditorEnabled()) {
            if (!mEditorRenderInfo)
                mEditorRenderInfo.reset(new FEditorRenderInfo());
            if (mEngine->GetEditorBrushVisible())
                RnDrawEditorBrush();
            if (mEngine->GetEditorSelectionVisible())
                RnDrawEditorSelection();
        }
        glPopMatrix();
        glPopAttrib();
    }
    catch (...) {
        glPopMatrix();
        glPopAttrib();
        throw;
    }
}

void CClient::RnDrawEditorBrush() {
    assert(mRnRendering);
    if (!mEngine->GetEditorBrushVisible() || !mEngine->GetEditorBrush())
        return;
    const shared_ptr<FTileLayer>& bl = mEngine->GetEditorBrushLayer();
    assert(bl);
    FVector2F pos = ConvertScreenToTileCoords(mMousePos);
    pos.X = floorf(pos.X);
    pos.Y = floorf(pos.Y);
    mEngine->SetEditorBrushPos(pos);

    glPushMatrix();
    glTranslatef(pos.X, pos.Y, 0.0f);
    RnDrawTileLayer(*bl.get());
    glPopMatrix();
}

void CClient::RnDrawEditorSelection() {
    assert(mRnRendering);

    const FRectF& selection = mEngine->GetEditorSelection();
    if (!mEngine->GetEditorSelectionVisible() ||
        selection.GetArea() < 1.0f)
        return;
    FEditorRenderInfo* ri = static_cast<FEditorRenderInfo*>(mEditorRenderInfo.get());
    GLfloat* sv = ri->SelectionVerticies;

    // Create vertices
    sv[0] = 0;
    sv[1] = 0;
    sv[2] = selection.Size.Width;
    sv[3] = 0;
    sv[4] = selection.Size.Width;
    sv[5] = selection.Size.Height;
    sv[6] = 0;
    sv[7] = selection.Size.Height;

    // Render
    glPushAttrib(GL_COLOR_BUFFER_BIT | GL_LINE_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);

    glLineWidth(2.0f);
    glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);

    glPushMatrix();
    glTranslatef(selection.Pos.X, selection.Pos.Y, 0.0f);
    glVertexPointer(2, GL_FLOAT, 0, sv);
    glDrawArrays(GL_LINE_LOOP, 0, 4);
    glPopMatrix();

    glDisableClientState(GL_VERTEX_ARRAY);
    glPopAttrib();
}

// Draw the actors in a specified level on the specified layer with visible
// coordinates (bounds) arguments
void CClient::RnDrawLevelActors(CLevel& level, int layerindex, float clipl, float clipt, float clipr, float clipb) {        
    CLevel::RenderablesType::const_iterator it;

    // Temporary full actor list iteration and simple clipping check
    for (it = level.GetRenderables().begin(); it != level.GetRenderables().end(); it++) {
        ARender* renderComp = it->get();
        if (!renderComp || renderComp->GetHidden())
            continue;
        const FVector3F& pos = renderComp->GetTransform()->GetPosition();
        if (pos.X < clipl || pos.X > clipr || pos.Y > clipb || pos.Y < clipt)
            continue;
        RnDrawActor(*renderComp);
    }
}

void CClient::RnDrawActor(ARender& a) {
    // Using temporary texture for now
    assert(mRnRendering);

    a.Draw(*this);

    // Draw bounding boxes around actors
    if (mRnShowActorBounds) {
        const shared_ptr<ATransform>& trans = a.GetOwner()->GetTransform();
        const shared_ptr<APhysics>& phys = a.GetOwner()->GetPhysics();
        if (trans && phys) {
            FRectF bounds = phys->GetBounds();
            if (bounds.GetArea() > 0.0f) {
                bounds.Pos.X += trans->GetPosition().X;
                bounds.Pos.Y += trans->GetPosition().Y;

                ClampRenderPosition(bounds.Pos);

                glPushAttrib(GL_COLOR_BUFFER_BIT | GL_LINE_BIT);

                glLineWidth(1.0f);
                glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

                glBegin(GL_LINE_LOOP);
                    glVertex2f(bounds.GetLeft(), bounds.GetTop());
                    glVertex2f(bounds.GetRight(), bounds.GetTop());
                    glVertex2f(bounds.GetRight(), bounds.GetBottom());
                    glVertex2f(bounds.GetLeft(), bounds.GetBottom());
                glEnd();

                glPopAttrib();
            }
        }
    }
}

void CClient::RniDrawSprite(
    const FVector3F& poso,
    const ui::Texture* texbase,
    const FSizeF& size,
    const ui::colour& color
) {
    if (!mRnRendering)
        throw RuntimeError("not currently rendering");
    if (!texbase)
        throw NullPointerError("RniDrawSprite(): texture");

    const ui::OpenGLTexture* tex = static_cast<const ui::OpenGLTexture*>(texbase);

    const ui::Vector2& scaling = tex->getTexelScaling();
    FVector3F pos = poso;
    ClampRenderPosition(pos);

    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, tex->getOpenGLTexture());
    glColor4f(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f);
        glVertex2f(pos.X, pos.Y);
        glTexCoord2f(size.Width * scaling.d_x, 0.0f);
        glVertex2f(pos.X + (size.Width / 16.0f), pos.Y);
        glTexCoord2f(size.Width * scaling.d_x, size.Height * scaling.d_y);
        glVertex2f(pos.X + (size.Width / 16.0f), pos.Y + (size.Height / 16.0f));
        glTexCoord2f(0.0f, size.Height * scaling.d_y);
        glVertex2f(pos.X, pos.Y + (size.Height / 16.0f));
    glEnd();
    glDisable(GL_TEXTURE_2D);
}

shared_array<GLfloat> CClient::RnGetTileVertexArray(FSizeI size) {
    std::map<FSizeI, shared_array<GLfloat>>::iterator it;

    // Find existing array of the correct size;
    it = mRnTileVertexArrays.find(size);

    if (it == mRnTileVertexArrays.end()) {
        // Generate a vertex array

        GLint tw = size.Width;
        GLint th = size.Height;

        // Tile count
        GLint tc = tw * th;

        // Allocate the array; tile count * vertexes per tile * vertex size
        GLsizei vas = tc * TILE_VERTEX_COUNT * TILE_VERTEX_SIZE;
        GLfloat* va = new GLfloat[vas];

        // Array index
        GLint i = 0;

        for (GLfloat y = 0; y < GLfloat(th); y++) {
            for (GLfloat x = 0; x < GLfloat(tw); x++) {
                // top left
                va[i + 0] = x;
                va[i + 1] = y;

                // top right
                va[i + 2] = x + 1.0f;
                va[i + 3] = y;

                // bottom right
                va[i + 4] = x + 1.0f;
                va[i + 5] = y + 1.0f;

                // bottom left
                va[i + 6] = x;
                va[i + 7] = y + 1.0f;

                i += 8;
            }
        }

        // Wrap, insert, and return array
        shared_array<GLfloat> sva(va);
        mRnTileVertexArrays[size] = sva;
        return sva;
    }
    else {
        return it->second;
    }
}

GLuint CClient::RnGetTileVertexBuffer(FSizeI size) {
    std::map<FSizeI, GLuint>::iterator it;

    // Find existing buffer
    it = mRnTileVertexBuffers.find(size);

    if (it == mRnTileVertexBuffers.end()) {
        shared_array<GLfloat> va = RnGetTileVertexArray(size);

        // hack: since we're buffering it, remove the array from the other map
        mRnTileVertexArrays.erase(size);

        GLuint vbo;

        // Vertex array size in bytes
        GLsizei vasb = (size.Width * size.Height) *
                        TILE_VERTEX_COUNT * TILE_VERTEX_SIZE * sizeof(GLfloat);

        glGenBuffersARB(1, &vbo);
        RnRegisterVertexBuffers(1, &vbo);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, vasb, va.get(), GL_STATIC_DRAW_ARB);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

        mRnTileVertexBuffers[size] = vbo;
        return vbo;
    }
    else {
        return it->second;
    }
}

void CClient::RnDrawTileLayer(FTileLayer& t) {
    if (mRnTilesetTexture == NULL)
        return;

    if (t.GetRenderInfoInvalid()) {
        delete t.GetRenderInfo();
        t.SetRenderInfo(NULL);
        t.SetRenderInfoInvalid(false);
    }
    if (t.GetRenderInfo() == NULL) {
        // Fill the drawable info by creating and buffering a tex coord array
        // and using an existing vertex array for the tiles

        const FTileData* td = t.GetTileData().get();

        // Tile size
        GLint tw = td->GetSize().Width;
        GLint th = td->GetSize().Height;

        // Tile count
        GLint tc = tw * th;

        // Tiles vector ref
        const tile_t* tls = &td->GetTiles().front();
            
        // X and y scale
        const ui::Vector2& scaling = mRnTilesetTexture->getTexelScaling();
        const float& xsc = scaling.d_x;
        const float& ysc = scaling.d_y;
            
        // array size
        GLint    tcas = tc * TILE_VERTEX_COUNT * TILE_VERTEX_SIZE;

        // vertex and tex coord arrays
        scoped_array<GLfloat> tca_(new GLfloat[tcas]);
        GLfloat* tca = tca_.get();

        // tile code and coordinates
        tile_t tcd;
        GLfloat tcx;
        GLfloat tcy;
            
        // array index
        GLint ti = 0;
        for (GLint y = 0; y < th; y++) {
            for (GLint x = 0; x < tw; x++) {
                tcd = tls[(y * tw) + x];
                tcx = GLfloat(TileToX(tcd));
                tcy = GLfloat(TileToY(tcd));

                // TOP LEFT
                tca[ti + 0] = (tcx * 16.0f) * xsc;
                tca[ti + 1] = (tcy * 16.0f) * ysc;

                // TOP RIGHT
                tca[ti + 2] = ((tcx + 1.0f) * 16.0f) * xsc;
                tca[ti + 3] = ((tcy       ) * 16.0f) * ysc;

                // BOTTOM RIGHT
                tca[ti + 4] = ((tcx + 1.0f) * 16.0f) * xsc;
                tca[ti + 5] = ((tcy + 1.0f) * 16.0f) * ysc;

                // BOTTOM LEFT
                tca[ti + 6] = ((tcx       ) * 16.0f) * xsc;
                tca[ti + 7] = ((tcy + 1.0f) * 16.0f) * ysc;

                ti += 8;
            }
        }
        // Retrieve vertex array buffer
        GLuint vavbo = RnGetTileVertexBuffer(td->GetSize());

        // Create texture coordinate vbo and buffer
        GLuint tcvbo;

        // Array size in bytes
        GLint tcasb = tcas * sizeof(GLfloat);

        glGenBuffersARB(1, &tcvbo);
        RnRegisterVertexBuffers(1, &tcvbo);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, tcvbo);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, tcasb, tca, GL_STATIC_DRAW_ARB);

        FTileLayerRenderInfo* info = new FTileLayerRenderInfo;

        info->Client = GetSelfWeakPtr<CClient>();
        info->VertexBuffer = vavbo;
        info->VertexSize = TILE_VERTEX_SIZE;
        info->VertexCount = tc * TILE_VERTEX_COUNT;
        info->VertexOffset = 0;
        info->VertexBufferOwned = false;
        info->TexCoordBuffer = tcvbo;
        info->TexCoordSize = TILE_TEX_COORD_SIZE;
        info->TexCoordCount = tc * TILE_TEX_COORD_COUNT;
        info->TexCoordOffset = 0;
        info->TexCoordBufferOwned = true;

        t.SetRenderInfo(info);
    }
    // Draw the vertexes that have been previously buffered
    FTileLayerRenderInfo* ri = static_cast<FTileLayerRenderInfo*>(t.GetRenderInfo());
    GLuint tex = static_cast<ui::OpenGLTexture*>(mRnTilesetTexture)->getOpenGLTexture();

    assert(ri && ri->VertexCount == ri->TexCoordCount);
        
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER_ARB, ri->VertexBuffer); 
    glVertexPointer(ri->VertexSize, GL_FLOAT, 0, (void*)ri->VertexOffset);

    glBindBufferARB(GL_ARRAY_BUFFER_ARB, ri->TexCoordBuffer);
    glTexCoordPointer(ri->TexCoordSize, GL_FLOAT, 0, (void*)ri->TexCoordOffset);

    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glDrawArrays(GL_QUADS, 0, ri->VertexCount);

    glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisable(GL_TEXTURE_2D);
}

void CClient::RnDeleteVertexBuffers() {
    for (uint32 i = 0; i < mRnVertexBuffers.size(); i++) {
        FBufferInfo& bi = mRnVertexBuffers[i];
        if (bi.Valid) {
            switch (bi.Type) {
            case BUFFER_TYPE_NONE:
                throw RuntimeError("valid vertex buffer with TYPE_NONE");
            case BUFFER_TYPE_VBO:
                glDeleteBuffersARB(1, &i);
                bi.Valid = false;
                bi.Type = BUFFER_TYPE_NONE;
                break;
            default:
                throw RuntimeError("unknown buffer type");
            }
        }
    }
}

void CClient::RnRegisterVertexBuffers(int num, GLuint* ids) {
    for (int i = 0; i < num; i++) {
        // Find a slot
        if (ids[i] < mRnVertexBuffers.size()) {
            FBufferInfo& bi = mRnVertexBuffers[ids[i]];
            if (mRnVertexBuffers[ids[i]].Valid) {
                throw RuntimeError("duplicate buffer registration");
            }
            else {
                bi.Valid = true;
                bi.Type = BUFFER_TYPE_VBO;
            }
        }
        // Do a resize
        else {
            mRnVertexBuffers.resize(ids[i]);
            FBufferInfo& bi = mRnVertexBuffers[ids[i]];
            bi.Valid = true;
            bi.Type = BUFFER_TYPE_VBO;
        }
    }
}

void CClient::RnUnregisterVertexBuffers(int num, GLuint* ids) {
    for (int i = 0; i < num; i++) {
        if (ids[i] < mRnVertexBuffers.size()) {
            FBufferInfo& bi = mRnVertexBuffers[ids[i]];
            if (bi.Valid) {
                bi.Valid = false;
                bi.Type = BUFFER_TYPE_NONE;
            }
            else {
                throw RuntimeError("buffer id invalid");
            }
        }
        else {
            // id is out of bounds
            throw RuntimeError("buffer id invalid and out of range");
        }
    }
}

void CClient::RnCompileTileset(shared_ptr<FTileset> tileset) {
    assert(tileset->TileDefs.size() > 0);

    FTilesetRenderInfo* ri = static_cast<FTilesetRenderInfo*>(tileset->GetRenderInfo());

    if (ri && ri->Texture)
        mUiRenderer->destroyTexture(*ri->Texture);

    ui::Size size(float(tileset->Size.Width), float(tileset->Size.Height));
    ui::Texture* tex = &mUiRenderer->createTexture(size);
    // Incomplete
    ui::OpenGLTexture* gltex = static_cast<ui::OpenGLTexture*>(tex);
}
                
//glBegin(GL_QUADS);
//    glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
//    glVertex2f(0.0f, 0.0f);
//    glVertex2f(64.0f, 0.0f);
//    glVertex2f(64.0f, 64.0f);
//    glVertex2f(0.0f, 64.0f);
//glEnd();
                
        
//glBegin(GL_QUADS);
//    //glColor4f(0.0f, 0.0f, 1.0f, 0.5f);
//    glVertex2f(              0.0f,                0.0f);
//    glVertex2f(subsize.Width,                0.0f);
//    glVertex2f(subsize.Width, subsize.GetHeight());
//    glVertex2f(              0.0f, subsize.GetHeight());
//glEnd();
        
//// is the target levelmap initialized?
//// You should not need to check this, Engine::SetLevelMap would initialize it
//if (mLevelMap->GetIsInitialized()) {

//    uint32 levelsWidth = uint32(ceil((vp.getWidth()/16)/mLevelMap->GetLevelWidth()))+2;
//    uint32 levelsHeight = uint32(ceil((vp.getHeight()/16)/mLevelMap->GetLevelHeight()))+2;
//    if (mLevelMap->GetLevelWidth() > vp.getWidth()/16)
//        levelsWidth = 2;
//    if (mLevelMap->GetLevelHeight() > vp.getHeight()/16)
//        levelsHeight = 2;

//    float tlx = floor((mCameraPos.GetX() - ((vp.getWidth()/2.0f)/16.0f)) / float(mLevelMap->GetLevelWidth()));
//    float tly = floor((mCameraPos.GetY() - ((vp.getHeight()/2.0f)/16.0f)) / float(mLevelMap->GetLevelHeight()));
//    
//    float offsetx = ((tlx*mLevelMap->GetLevelWidth())-mCameraPos.GetX())*16.0f;
//    float offsety = ((tly*mLevelMap->GetLevelHeight())-mCameraPos.GetY())*16.0f;

//    glTranslatef(
//        (vp.getWidth()/2)+offsetx,
//        (vp.getHeight()/2)+offsety,
//        0
//        );
//    // Alpha testing and depth test write pass
//    glEnable(GL_TEXTURE_2D);
//    
//    glEnable( GL_ALPHA_TEST );
//    glEnable( GL_DEPTH_TEST ); // enable depth and alpha test
//    glAlphaFunc(GL_EQUAL,1.0); // pass if the alpha is one (solid geometry, no blending)
//    glDepthFunc(GL_LESS); // pass if its closer
//    glDepthMask(GL_TRUE);
//    
//    uint32 levelx, levely, level;
//    float z_travel = 0.0f;
//    Level* levelptr;

//    for (uint32 j = 0 ; j < levelsHeight ; j ++) {
//        for (uint32 i = 0 ; i < levelsWidth ; i ++) {
//            levelx = uint32(tlx+i);
//            levely = uint32(tly+j);
//            // make sure levelx and levely are within the total array
//            if (levelx > mLevelMap->GetLevelsX() - 1 || levely > mLevelMap->GetLevelsY() - 1)
//                continue;
//            level = (levely * mLevelMap->GetLevelsX()) + levelx;
//            // You should have done this before, instead of repeating "(*mLevelMap)[level]" ~Inverness
//            // No need to use shared_ptr for rendering
//            levelptr = (*mLevelMap)[level].get();
//            glPushMatrix();
//            glTranslatef(GLfloat(i * mLevelMap->GetLevelWidth() * 16),
//                         GLfloat(j * mLevelMap->GetLevelHeight() * 16),
//                         GLfloat(levelptr->GetLayerZHeight(levelptr->GetLayerCount())));
//            
//            for (sint32 target_layer = levelptr->GetLayerCount()-1 ; target_layer>=0 ; target_layer--) {
//                if (levelptr->GetRenderMode(target_layer) == RENDER_SOLID) {
//                    glTranslatef(0.0f, 0.0f, z_travel);
//                    glColor3ub(255, 255, 255);
//                    levelptr->GetLayer(target_layer)->Draw();
//                    if (target_layer > 0)
//                        z_travel = levelptr->GetLayerZHeight(target_layer - 1) - levelptr->GetLayerZHeight(target_layer);
//                }
//                else if (levelptr->GetRenderMode(target_layer) == RENDER_BLENDED)
//                {
//                    blist.insert(
//                        blist.begin(),
//                        Blended(
//                            levelptr->GetLayer(target_layer),
//                            PointF(float(i * mLevelMap->GetLevelWidth() * 16),
//                                   float(j * mLevelMap->GetLevelHeight() * 16))
//                            )
//                        );
//                }
//            }
//            glPopMatrix();
//        }
//    }
//    glDepthMask(GL_FALSE); // disable depth writing since blendeds dont overwrite
//    glDisable(GL_ALPHA_TEST);

//    // TODO: add other IDrawable like entities
//    std::sort(blist.begin(),blist.end(),BlendCompare);
//    // enable blended objects
//    glEnable(GL_BLEND);
//    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//    for (std::vector<Blended>::iterator i = blist.begin() ; i != blist.end() ; i++) {
//        glPushMatrix();
//        // translate to the calculated screen x/y for this object
//        glTranslatef(i->Offset.GetX(),i->Offset.GetY(),i->Target->GetZ());
//        i->Target->Draw();
//        glPopMatrix();
//    }
//    // disable z-writes and alpha testing
//    
//    glDepthMask(GL_FALSE);
//    glDisable(GL_ALPHA_TEST);
//    glDisable(GL_DEPTH_TEST);

//    // Draw the current brush if any
//    if (mCurrentBrush != NULL && mIsEditing == true) {
//        glEnable(GL_TEXTURE_2D);
//        glEnableClientState(GL_VERTEX_ARRAY);
//        glPushMatrix();
//        glTranslatef(
//            ( 0-(((tlx*mLevelMap->GetLevelWidth())*16) )) + (mBrushPos.GetX()*16),
//            ( 0-(((tly*mLevelMap->GetLevelHeight())*16) )) + (mBrushPos.GetY()*16),
//            0.0f);
//        mCurrentBrush->Draw();
//        glPopMatrix();
//        glDisableClientState(GL_VERTEX_ARRAY);
//    }

//    // Draw the selection box if any
//    if (mSelectionTopLeft != mSelectionBotRight) {
//        glDisable(GL_TEXTURE_2D);
//        glBlendFunc(GL_ONE_MINUS_DST_COLOR,GL_ONE_MINUS_SRC_COLOR);
//        glEnableClientState(GL_VERTEX_ARRAY);
//        glLineWidth(2.0f);
//        glPushMatrix();
//        glTranslatef(
//            // untranslate
//           ( 0-(((tlx*mLevelMap->GetLevelWidth())*16) )) + (mSelectionTopLeft.GetX()*16),
//           ( 0-(((tly*mLevelMap->GetLevelHeight())*16) )) + (mSelectionTopLeft.GetY()*16),
//            0.0f);
//        glVertexPointer(2,GL_FLOAT,0,mSelectionVerts);
//        glDrawArrays(GL_LINE_STRIP,0,5);
//        glPopMatrix();
//        glDisableClientState(GL_VERTEX_ARRAY);
//    }
//    // Draw a nice little border around each level :)
//    if (mShowGrid == true)  {
//        if (mGridVerts != NULL)
//        {
//            glDisable(GL_TEXTURE_2D);
//            glBlendFunc(GL_ONE_MINUS_DST_COLOR,GL_ONE_MINUS_SRC_COLOR);
//            glEnableClientState(GL_VERTEX_ARRAY);
//            glLineWidth(1.0f);
//            glColor3ub(255,255,255);//mGridColors[0],mGridColors[1],mGridColors[2]);
//            for (uint32 j = 0 ; j < levelsHeight ; j ++) {
//                for (uint32 i = 0 ; i < levelsWidth ; i ++) {
//                    levelx = uint32(tlx+i);
//                    levely = uint32(tly+j);
//                    // make sure levelx and levely are within the total array
//                    if (levelx > mLevelMap->GetLevelsX()-1 || levely > mLevelMap->GetLevelsY()-1)
//                        continue;

//                    glPushMatrix();
//                    glTranslatef(GLfloat(i * mLevelMap->GetLevelWidth() * 16),
//                                 GLfloat(j * mLevelMap->GetLevelHeight() * 16),
//                                 0.0f);
//                    glVertexPointer(2, GL_FLOAT, 0, mGridVerts);
//                    glDrawArrays(GL_LINES, 0, 8);//(mLevelMap->GetLevelWidth()*2)+(mLevelMap->GetLevelHeight()*2));
//                    glPopMatrix();
//                }
//            }
//            glDisableClientState(GL_VERTEX_ARRAY);
//        }
//    }
//    glDisable(GL_BLEND);
//}

}} //namespace Kharlia